Skip to content

[sql-33] session: add migration code from kvdb to SQL #1051

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

ViktorTigerstrom
Copy link
Contributor

This PR introduces the migration logic for transitioning the sessions store from kvdb to SQL.

Note that as of this PR, the migration is not yet triggered by any production code, i.e. only tests execute the migration logic.

Part of #917

@ViktorTigerstrom ViktorTigerstrom added the no-changelog This PR is does not require a release notes entry label Apr 29, 2025
@ViktorTigerstrom ViktorTigerstrom force-pushed the 2025-04-migrate-sessions branch from 449a2cc to 1828ec4 Compare April 29, 2025 17:52
@ViktorTigerstrom ViktorTigerstrom force-pushed the 2025-04-migrate-sessions branch from 1828ec4 to a868a93 Compare May 2, 2025 09:04
Copy link
Contributor Author

@ViktorTigerstrom ViktorTigerstrom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding some review comments where input would be extra helpful :)!

Comment on lines +748 to +753
macRecipe = &MacaroonRecipe{}
if perms != nil {
macRecipe.Permissions = unmarshalMacPerms(perms)
}
if caveats != nil {
macRecipe.Caveats = unmarshalMacCaveats(caveats)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if you think we should rather fix the kvdb store implementation rather than the sql one. But as we've previously opted for not updating the store behaviour, and make the sql one mimic the behaviour until we've completed the migration, I opted for this version here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, I could also override this in newly added overrideMacaroonRecipe function before the migration deep equals check. But I kept this as is, as I do think it's correct behaviour that we don't set the Permissions or Caveats and keep them at nil, if no value actually exists for them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think in this case it is fine to fix on the kvdb side so that our sql side can stay neat?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also commit message says fix kvdb&sql but this only touches sql

Comment on lines 193 to 225
if !session.RevokedAt.IsZero() {
err = tx.SetSessionRevokedAt(
ctx,
makeSetRevokedAtParams(sqlId, session.RevokedAt),
)
if err != nil {
return err
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could potentially update the insert session sql query to also take an optional RevokedAt sql.NullTime param, to not have to do this as a separate step. I opted for this version to keep things as is as much as possible, but if you think that'd be better, I'll update to do that instead :)

Comment on lines 306 to 336
{
"randomized sessions",
randomizedSessions,
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opted for a test that uses the "standard" rand lib here, as @bitromortac had a preference for that in #1047 (comment). But can also look into adding a test that uses rapid if you'd prefer.

Comment on lines +596 to +647
// We always have a caveat.Id, but the rest are
// randomized if they exist or not.
macCaveat.Id = randomBytes(rand.Intn(10) + 1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if this is not true in for real world data, as migrations WILL fail if we don't set a caveat.Id when the caveats param of WithMacaroonRecipe is not nil.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could potentially override this as well in the new overrideMacaroonRecipe as well. But I do not think this scenario can happen for real world data. But open to implementing that as well if you think it's worth doing it just to be sure.

// "dest" state, without checking if the state transition is legal.
//
// NOTE: this function should only be used for testing purposes.
func (db *BoltStore) shiftStateUnsafe(_ context.Context, id ID,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not fully sure if we add test functions like this one that's a struct member to the _test file, or if we add it in the actual struct file?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, not too great, you could change to func shiftStateUnsafe(_ context.Context, db *BoltStore, id ID, dest State) error {

Copy link
Contributor

@bitromortac bitromortac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, very clean on first pass! 🔥

Comment on lines 321 to 322
// The remote public key is currently only set for autopilot sessions,
// else it's an empty byte array.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think it should read else it's a nil byte array, since we compare to nil in the code in other sites, which is also correctly set above with the var statement

require.NoError(t, err)

t.Cleanup(func() {
require.NoError(t, kvStore.DB.Close())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny nit: the embedded DB can be removed

Comment on lines 356 to 410
// numberOfSessions is set to 100 to add enough sessions to get
// enough variation between number of invoices and payments, but
// kept low enough for the test not take too long to run, as the
// test time increases drastically by the number of sessions we
// migrate.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the doc mentions invoice and payments, which is probably a copy/paste

// "dest" state, without checking if the state transition is legal.
//
// NOTE: this function should only be used for testing purposes.
func (db *BoltStore) shiftStateUnsafe(_ context.Context, id ID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right, not too great, you could change to func shiftStateUnsafe(_ context.Context, db *BoltStore, id ID, dest State) error {

Copy link
Member

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

holding off on review here until the prep PR discussed in the accounts mig PR is in (since that will be used to test this one)

@ViktorTigerstrom
Copy link
Contributor Author

Now that #1047 has been merged, I'll update this PR to adhere to the feedback brought up in that PR, and then re-request reviews afterwards.

In preparation for upcoming migration tests from a kvdb to an SQL store,
this commit updates the NewTestDB function to return the Store interface
rather than a concrete store implementation.

This change ensures that migration tests can call NewTestDB under any
build tag while receiving a consistent return type.
@lightninglabs-deploy
Copy link

@ViktorTigerstrom, remember to re-request review from reviewers when ready

When a session has a MacaroonRecipe set, which has a nil value set for
either the `Permissions` or the `Caveats` field, the kvdb session store
would return that field as nil, while the sql prior to this commit
would return an empty array.

When the we migrate the session store from kvdb to sql, that will cause
the kvdb and the migrate session store to differ when comparing them,
which will cause the migration to fail.

This commit fixes that by ensuring that the sql session store returns
a nil value for those fields in that scenario, rather than an empty
array.
@ViktorTigerstrom ViktorTigerstrom force-pushed the 2025-04-migrate-sessions branch from a868a93 to 4b3b39f Compare May 27, 2025 10:09
@ViktorTigerstrom
Copy link
Contributor Author

Updated the PR to also address feedback raised in #1047! Will address the small linting issue after next review round :)

Copy link
Contributor

@bitromortac bitromortac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, utACK 🎉

},
}

err = tx.InsertSessionMacaroonCaveat(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: fits on single line, same for InsertSessionPrivacyFlagParams, I found this in other places as well, where you could contract a bit more

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I addressed that + other places that I found, so I hope this has been addressed now.

Copy link
Contributor Author

@ViktorTigerstrom ViktorTigerstrom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the review @bitromortac 🎉!! I noticed that we can have real world data that sets a non-nil macaroon recipe, but with nil perms& caveats. I updated the code to be able to handle that case as well.

},
},
{
name: "macaroon recipe with nil perms and caveats",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this test is new, as this can actually happen for real world sessions.

Comment on lines +748 to +753
macRecipe = &MacaroonRecipe{}
if perms != nil {
macRecipe.Permissions = unmarshalMacPerms(perms)
}
if caveats != nil {
macRecipe.Caveats = unmarshalMacCaveats(caveats)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, I could also override this in newly added overrideMacaroonRecipe function before the migration deep equals check. But I kept this as is, as I do think it's correct behaviour that we don't set the Permissions or Caveats and keep them at nil, if no value actually exists for them.

Comment on lines +596 to +647
// We always have a caveat.Id, but the rest are
// randomized if they exist or not.
macCaveat.Id = randomBytes(rand.Intn(10) + 1)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could potentially override this as well in the new overrideMacaroonRecipe as well. But I do not think this scenario can happen for real world data. But open to implementing that as well if you think it's worth doing it just to be sure.

This commit introduces the migration logic for transitioning the
sessions store from kvdb to SQL.

Note that as of this commit, the migration is not yet triggered by any
production code, i.e. only tests execute the migration logic.
@ViktorTigerstrom ViktorTigerstrom force-pushed the 2025-04-migrate-sessions branch from 8c970bf to 46873cb Compare May 28, 2025 12:45
Copy link
Member

@ellemouton ellemouton left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good!

Comment on lines +748 to +753
macRecipe = &MacaroonRecipe{}
if perms != nil {
macRecipe.Permissions = unmarshalMacPerms(perms)
}
if caveats != nil {
macRecipe.Caveats = unmarshalMacCaveats(caveats)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think in this case it is fine to fix on the kvdb side so that our sql side can stay neat?

Comment on lines +748 to +753
macRecipe = &MacaroonRecipe{}
if perms != nil {
macRecipe.Permissions = unmarshalMacPerms(perms)
}
if caveats != nil {
macRecipe.Caveats = unmarshalMacCaveats(caveats)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also commit message says fix kvdb&sql but this only touches sql

Comment on lines +40 to +43
// If sessions are linked to a group, we must insert the initial session
// of each group before the other sessions in that group. This ensures
// we can retrieve the SQL group ID when inserting the remaining
// sessions. Therefore, we first insert all initial group sessions,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting - are we not able to rely on the fact that sessions are stored in order in kvdb? ie, if we get a session with a group ID set, that will always either point to the session at hand or a session we've already handled?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ie, i think we can just:

for s := sessions {
       if s.groupID  = s.ID {
                    <insert new session. no coupled group>
                   continue
       }
       
       groupID := db.GetGroupIDByAlias(s.groupID)
       
        <insert new session. couple to group>
}  

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or did you actually run into a scenario where these were not in order?

Comment on lines +152 to +153
session.AccountID.WhenSome(func(alias accounts.AccountID) {
// Check that the account exists in the SQL store, before
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

technically what we are doing here is just getting the account DB level ID.
we dont actually need to "check before link" since SQL does that for us.

return
}

acctID = sql.NullInt64{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sqldb.SQLInt64(accDBID)

if !session.RevokedAt.IsZero() {
setSessionRevokedParams := sqlc.SetSessionRevokedAtParams{
ID: sqlId,
RevokedAt: sql.NullTime{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sqldb.SQLTime

Comment on lines +209 to +211
// Since the InsertSession query doesn't support that we set the revoked
// field during the insert, we need to set the field after the session
// has been created.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +230 to +231
// session itself, and therefore the SQL id for the session won't exist
// prior to inserting the session.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// Now lets set the group ID for the session.
err = tx.SetSessionGroupID(ctx, sqlc.SetSessionGroupIDParams{
ID: sqlId,
GroupID: sql.NullInt64{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sqldb.SQLInt64

Comment on lines +306 to +313
privacyFlagParams := sqlc.InsertSessionPrivacyFlagParams{
SessionID: sqlId,
Flag: int32(privacyFlag),
}

err = tx.InsertSessionPrivacyFlag(ctx, privacyFlagParams)
if err != nil {
return err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this and many of the above can just be in-lined instead of having separate param vars

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no-changelog This PR is does not require a release notes entry
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants